package com.gauravk.audiovisualizer.visualizer;

import com.gauravk.audiovisualizer.base.BaseVisualizer;

import com.gauravk.audiovisualizer.model.PaintStyle;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.app.Context;

import org.jetbrains.annotations.Nullable;

/**
 * 折线图
 *
 * @author maple on 2019/4/25 10:17.
 * @version v1.0
 */
public class HiFiVisualizer extends BaseVisualizer implements Component.DrawTask {
    private static final int BAR_MAX_POINTS = 240;
    private static final int BAR_MIN_POINTS = 30;
    private static final float PER_RADIUS = .65f;
    private int mRadius;
    private int mPoints;
    private int[] mHeights;
    private Paint mPaint;
    // outward path
    private Path mPath;

    // inward path
    private Path mPath1;

    /**
     * This is the distance from center to bezier control point.
     * We can calculate the bezier control points of each segment this distance and its angle;
     */
    private int mBezierControlPointLen;

    /**
     * 使用java代码实例化调用
     *
     * @param context
     */
    public HiFiVisualizer(Context context) {
        super(context);
    }

    /**
     * 使用xml布局引用调用
     *
     * @param context
     * @param attrs
     */
    public HiFiVisualizer(Context context, @Nullable AttrSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void init() {
        addDrawTask(this::onDraw);
        mRadius = -1;
        mPath = new Path();
        mPath1 = new Path();

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(1.0f);
        mPoints = (int) (BAR_MAX_POINTS * mDensity);
        if (mPoints < BAR_MIN_POINTS) {
            mPoints = BAR_MIN_POINTS;
        }
        mHeights = new int[mPoints];

        mPaint.setColor(new Color(Color.getIntColor("#FCB8A3")));
        mPaint.setStrokeWidth(mStrokeWidth);
        if (mPaintStyle == PaintStyle.FILL) {
            mPaint.setStyle(Paint.Style.FILL_STYLE);
        } else {
            mPaint.setStyle(Paint.Style.STROKE_STYLE);
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mRadius == -1) {
            mRadius = (int) (Math.min(getWidth(), getHeight()) / 2 * PER_RADIUS);
            mBezierControlPointLen = (int) (mRadius / Math.cos(Math.PI / mPoints));
        }
        updateData();
        mPath.reset();
        mPath1.reset();

        // start the outward path from the last point
        float cxL = (float) (getWidth() / 2 + Math.cos((360 - (double)(360 / mPoints)) * Math.PI / 180) * (mRadius + mHeights[mPoints - 1]));
        float cyL = (float) (getHeight() / 2 - Math.sin((360 - (double)(360 / mPoints)) * Math.PI / 180) * (mRadius + mHeights[mPoints - 1]));
        mPath.moveTo(cxL, cyL);

        // start the inward path from the last point
        float cxL1 = (float) (getWidth() / 2 + Math.cos((360 - (double)(360 / mPoints)) * Math.PI / 180) * (mRadius - mHeights[mPoints - 1]));
        float cyL1 = (float) (getHeight() / 2 - Math.sin((360 - (double)(360 / mPoints)) * Math.PI / 180) * (mRadius - mHeights[mPoints - 1]));
        mPath1.moveTo(cxL1, cyL1);
        for (int i = 0; i < 360; i = i + 360 / mPoints) {
            // outward
            // the next point of path
            float cx = (float) (getWidth() / 2 + Math.cos(i * Math.PI / 180) * (mRadius + mHeights[i * mPoints / 360]));
            float cy = (float) (getHeight() / 2 - Math.sin(i * Math.PI / 180) * (mRadius + mHeights[i * mPoints / 360]));

            // second bezier control point
            float bx = (float) (getWidth() / 2 + Math.cos((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen + mHeights[i * mPoints / 360]));
            float by = (float) (getHeight() / 2 - Math.sin((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen + mHeights[i * mPoints / 360]));
            int lastPoint = i == 0 ? mPoints - 1 : i * mPoints / 360 - 1;

            // fist bezier control point
            float ax = (float) (getWidth() / 2 + Math.cos((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen + mHeights[lastPoint]));
            float ay = (float) (getHeight() / 2 - Math.sin((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen + mHeights[lastPoint]));
            mPath.cubicTo(ax, ay, bx, by, cx, cy);

            // inward
            float cx1 = (float) (getWidth() / 2 + Math.cos(i * Math.PI / 180) * (mRadius - mHeights[i * mPoints / 360]));
            float cy1 = (float) (getHeight() / 2 - Math.sin(i * Math.PI / 180) * (mRadius - mHeights[i * mPoints / 360]));
            float bx1 = (float) (getWidth() / 2 + Math.cos((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen - mHeights[i * mPoints / 360]));
            float by1 = (float) (getHeight() / 2 - Math.sin((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen - mHeights[i * mPoints / 360]));
            float ax1 = (float) (getWidth() / 2 + Math.cos((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen - mHeights[lastPoint]));
            float ay1 = (float) (getHeight() / 2 - Math.sin((i - (double)((180 / mPoints))) * Math.PI / 180) * (mBezierControlPointLen - mHeights[lastPoint]));
            mPath1.cubicTo(ax1, ay1, bx1, by1, cx1, cy1);
            canvas.drawLine(cx, cy, cx1, cy1, mPaint);
        }
        canvas.drawPath(mPath, mPaint);
        canvas.drawPath(mPath1, mPaint);
    }

    private void updateData() {
        if (isVisualizationEnabled && mRawAudioBytes != null) {
            if (mRawAudioBytes.length == 0) return;
            for (int i = 0; i < mHeights.length; i++) {
                int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / mPoints));
                int t = 0;
                if (x < 1024) {
                    t = ((byte) (Math.abs(mRawAudioBytes[x]) + 128)) * mRadius / 128;
                }
                mHeights[i] = -t;
            }
        }
    }

    public void setVisualizationEnabled(boolean b) {
        isVisualizationEnabled = b;
    }
}
